<template>
  <div ref="joint" id="paper"></div>
</template>

<script>
// npm install dagre graphlib jointjs svg-pan-zoom --save
import dagre from "dagre";
import graphlib from "graphlib";
import svgPanZoom from "svg-pan-zoom";
import customShape from "@/plugins/shape";

export default {
  name: "JointPaper",
  data() {
    return {
      paper: null,
      /** 处理后生成的节点 */
      nodeList: [],
      /** 处理后生成的连线 */
      linkList: [],
      /** 当前单元格，joint的拖动和svgpanzoom会冲突造成抖动 */
      currCell: null,
      /** 原始数据：节点 */
      nodes: [],
      /** 原始数据：连线 */
      links: [],
    };
  },
  props: {
    width: {
      type: [String, Number],
      default: 800,
    },
    height: {
      type: [String, Number],
      default: 600,
    },
    gridSize: {
      type: Number,
      default: 1,
    },
    drawGrid: {
      type: [Object, Boolean],
      default: false,
    },
    background: {
      type: [Object, Boolean],
      default: false,
    },
    readonly: {
      type: Boolean,
      default: false,
    },
  },

  created() {
    this.name = this.$options.name;console.log(`[${this.name}] Created`);

    this.graph = new this.$joint.dia.Graph(
      {},
      { cellNamespace: this.$joint.shapes }
    );
  },

  methods: {
    init() {
      this.initGraph();
    },
    setupGraphLink(setLineColor = '#999', links = this.links) {
      console.log(links, '-----------');
      // let setLineColor = lineColor || '#999'
      // let links = this.links || links
      links.forEach((ele) => {
        let link = new this.$joint.shapes.standard.Link({
          source: {
            id: ele.from,
            /** 从out端口连出线 */
            port: ["out"],
          },
          target: {
            id: ele.to,
            /** 从in端口入线 */
            port: ["in"],
          },
          labels: [
            {
              position: 0.8,
              attrs: {
                text: {
                  text: 'X' || '',
                  'font-weight': 'bold',
                  'font-size': '12px'
                }
              }
              // markup: [{
              //   tagName: 'rect',
              //   selector: 'labelBody'
              // }, {
              //   tagName: 'text',
              //   selector: 'labelText'
              // }],
              // attrs: {
              //   labelText: {
              //     text: "x",
              //     fill: 'black',
              //     textAnchor: 'middle',
              //     refY: '-35%',
              //     // refY2: '-50%',
              //     fontSize: 20,
              //     cursor: 'pointer'
              //   },
              //   labelBody: {
              //     fill: 'grey',
              //     stroke: 'grey',
              //     strokeWidth: 2,
              //     refWidth: '100%',
              //     refHeight: '100%',
              //     refX: '-50%',
              //     refY: '-50%',
              //     rx: 50,
              //     ry: 50
              //   }
              // },
              // size: {
              //   width: 30,
              //   height: 30
              // }
            }
          ],
          attrs: {
            line: {
              stroke: setLineColor,
              strokeWidth: 3,
              // strokeDasharray: '2 1', // 虚化
              // sourceMarker: { //// 开始箭头
              // 	type: "path",
              // 	d: "M 20 -10 0 0 20 10 Z",
              // },
              targetMarker: { //// 结束箭头
                type: "path",
                // d: "M 0 -0 0 0 0 0 Z",
                d: "M 10 -5 0 0 10 5 Z",
                //     'r': 10,
                // 'cx': 10,
              },
            },
          },
        });
        // jointjs-links ===> https://blog.csdn.net/weixin_42995876/article/details/90266030
        // link.router('manhattan');
        // link.router('metro');
        // link.router('normal');
        // link.router('orthogonal'); // 正交线
        // link.router('orthogonal',{padding:10});
        // link.router('oneSide');
        link.router('manhattan');  // 路由器
        // link.connector('jumpover', { size: 10 }); //设置线条之间不叠压的样式
        // link.connector('normal');
        // link.connector('rounded'); // 圆角
        // link.connector('rounded',{radius:20});
        // link.connector('smooth');
        link.connector('rounded', { radius: 20 });  // 连接器
        this.linkList.push(link);
      });
      this.graph.addCell(this.linkList);
    },
    setupGraph() {
      console.log(this.nodes, '=============');

      this.nodes.forEach((ele) => {
        // 1 文本换行
        let breaked_text = this.$joint.util.breakText(ele.label, {
          width: 100,
        });
        let line_num = breaked_text.split('\n').length;
        // 2 文本换行
        // let node = new customShape({            // 自定义节点
        // let node = new this.$joint.shapes.standard.Rectangle({
        let node = new customShape({          // 自定义html
          id: ele.id,
          size: {
            width: 100,
            height: 50,
          },
          /** 增加你需要的端口，端口是数组，可以传多个值，位置会默认均匀分布在你所规定的边上 */
          inPorts: ["in"],
          outPorts: ["out"],
          attrs: {
            body: {
              fill: "#ddd",
              stroke: "none",
            },
            text: {
              text: breaked_text,
            },
          },
        });
        //3 文本换行
        node.resize(100, line_num * 30);

        this.nodeList.push(node);
      });

      this.graph.addCell(this.nodeList);
      // this.nodeList.addTo(graph);

      this.setupGraphLink();


    },
    /** 初始化画布 */
    initGraph() {
      this.nodeList = [];
      this.linkList = [];
      this.paper = new this.$joint.dia.Paper({
        dagre: dagre,
        graphlib: graphlib,
        el: this.$refs.joint, // 图形容器
        cellViewNamespace: this.$joint.shapes,
        model: this.graph, // 图形数据，此处可绑定graph
        width: this.width, // 图形宽度
        height: this.height,
        gridSize: this.gridSize, // 参考线密度
        drawGrid: this.drawGrid, // 栅格参考线
        background: this.background, // 背景
        interactive: !this.readonly, // 控制元素的交互属性 （例如是否可以移动）
        highlighting: { // 参数用于控制如何展示高亮组件
          default: {
            name: "addClass",
            options: {
              className: "some-custom-class",
            },
          },
        },
      });
      // 点击元素
      this.paper.on('blank:pointerclick', () => {
        this.blankClick(this.paper)
      })
      this.paper.on('cell:pointerclick', (cellView) => {
        this.cellClick(cellView, this.paper)
        // cellView.highlight(null, {
        //   highlighter: {
        //     name: 'stroke',
        //     options: {
        //       padding: 10,
        //       rx: 5,
        //       ry: 5,
        //       attrs: {
        //         'stroke-width': 4,
        //         stroke: '#2185f0'
        //       }
        //     }
        //   }
        // });
      });
      // 删除线
      // this.paper.on({
      //   'link:mouseenter': (linkView) => {
      //     linkView.addTools(new this.$joint.dia.ToolsView({
      //       tools: [
      //         new this.$joint.linkTools.Vertices({ snapRadius: 0 }),
      //         new this.$joint.linkTools.Remove({
      //           distance: 20
      //         }),
      //         new this.$joint.linkTools.Button({
      //           markup: [{
      //             tagName: 'circle',
      //             selector: 'button',
      //             attributes: {
      //               'r': 15,
      //               'stroke': '#fe854f',
      //               'stroke-width': 1,
      //               'fill': 'white',
      //               'cursor': 'pointer'
      //             }
      //           }, {
      //             tagName: 'text',
      //             textContent: 'X',
      //             selector: 'icon',
      //             attributes: {
      //               'fill': '#fe854f',
      //               'font-size': 8,
      //               'text-anchor': 'middle',
      //               'font-weight': 'bold',
      //               'pointer-events': 'none',
      //               'y': '0.3em'
      //             }
      //           }],
      //           distance: -50,
      //           action: function () {
      //             var link = this.model;
      //             link.remove();
      //           }
      //         })
      //       ]
      //     }));
      //   }
      // })
    },
    /** 创建节点 */
    createNode() {
      this.setupGraph()
    },
    /** 创建连线 */
    /** 画布节点自动布局 */
    randomLayout() {
      console.log(this.$joint);
      this.$joint.layout.DirectedGraph.layout(this.graph, {
        dagre: dagre,
        graphlib: graphlib,
        /** 布局方向 TB | BT | LR | RL */
        rankDir: "LR",
        /** 表示列之间间隔的像素数 */
        rankSep: 200,
        /** 相同列中相邻接点之间的间隔的像素数 */
        nodeSep: 80,
        /** 同一列中相临边之间间隔的像素数 */
        edgeSep: 80,
      });
    },
    /** svgpanzoom 画布拖拽、缩放 */
    svgPanZoom() {
      if (this.nodes.length) {
        let svgZoom = svgPanZoom("#paper svg", {
          /** 是否可拖拽 */
          panEnabled: true,
          /** 是否可缩放 */
          zoomEnabled: true,
          /** 双击放大 */
          dblClickZoomEnabled: false,
          /** 可缩小至的最小倍数 */
          minZoom: 0.01,
          /** 可放大至的最大倍数 */
          maxZoom: 100,
          /** 是否自适应画布尺寸 */
          fit: true,
          /** 图是否居中 */
          center: true,
          /** 判断是否是节点的拖拽 */
          beforePan: (oldPan, newPan) => {
            if (this.currCell) {
              return false;
            }
          },
        });
        svgZoom.setZoomScaleSensitivity(0.5);
        /** fit:true 元素数量较少时，会引起元素过度放大，当缩放率大于1时，将图像缩小为1；小于等于1时，为体现出边距更显美观，整体缩放至0.9 */
        let { sx, sy } = this.paper.scale();
        if (sx > 1) {
          svgZoom.zoom(1 / sx);
        } else {
          svgZoom.zoom(0.9);
        }
      }
    },
    paperEvent() {
      this.paper.on("element:pointerdown", (cellView, evt, x, y) => {
        this.currCell = cellView;
      });
      this.paper.on("cell:pointerup blank:pointerup", (cellView, evt, x, y) => {
        this.currCell = null;
      });
    },
    blankClick(paper) {
      this.graph.getCells().forEach(cell => {
        // console.log(cell);
        this.$joint.highlighters.mask.remove(cell.findView(paper))
      })
      this.setupGraphLink('#999')
    },
    cellClick(cellView, paper) {
      // console.log(cellView.model.id);
      // console.log(this.links)

      let linksArr = [];

      const nodeId = cellView.model.id;
      this.links.forEach((item) => {
        if (item.from === nodeId || item.to === nodeId) {
          console.log(item)
          linksArr.push(item);
        }
      })

      this.setupGraphLink('#2185f0', linksArr)


      this.graph.getCells().forEach(cell => {
        // console.log(cell.findView(paper));
        this.$joint.highlighters.mask.remove(cell.findView(paper))
      })
      this.$joint.highlighters.mask.add(cellView, { selector: '.rotatable' }, 'body-highlight', {
        // deep: true,
        attrs: {
          'stroke': '#2185f0',
          'stroke-width': 3,
          padding: 10,
          rx: 5,
          ry: 5,
        }
      });
    },
  },
  watch:{
    nodes(val){
      if (!val.length) return;
      this.createNode();
      this.randomLayout();
      this.svgPanZoom();
      this.paperEvent();
    }
  },
  mounted() {
    console.log(`[${this.name}] Mounted:`, this.$refs.joint);
    this.init();
    setTimeout(() => {
      // 插入十万条数据
      const total = 40
      // 一次插入 20 条，如果觉得性能不好就减少
      const once = 40
      // 渲染数据总共需要几次
      const loopCount = total / once
      let countOfRender = 0
      let node = []
      let link = []
      function add() {
        // 优化性能，插入不会造成回流
        
        for (let index = 0; index < once; index++) {
          node.push({ id: index+1, label: "node" + (+index+1) })
          link.push({ from: 1, to: index+1 })
        }

        countOfRender += 1
        loop()
      }
      function loop() {
        if (countOfRender < loopCount) {
          window.requestAnimationFrame(add)
        }
      }
      loop()


      this.nodes = node;
      this.links = link;

      // this.nodes = [
      //   { id: 1, label: "node1node1node1" },
      //   { id: 2, label: "node2" },
      //   { id: 3, label: "node3" },
      // ];
      // this.links = [
      //   { from: 1, to: 2 },
      //   { from: 1, to: 3 },
      // ]
    }, 0)
    // this.$emit("init", this.graph);
  },
};
</script>

<style scoped>
.some-custom-class {
  background: red;
}
</style>
